﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using Ookii.Dialogs.Wpf;
using Samurai.Wakizashi.Workflow;
using Samurai.WorkflowGraph.Models;
using Samurai.WorkflowGraph.ViewModels;
using Samurai.WorkflowGraph.Views;

namespace Samurai.WorkflowGraph.Workflow
{
    internal class GraphWorkflow : SequentialWorkflow<GraphWorkflowContext>
    {
        public GraphWorkflow()
        {
            Context = new GraphWorkflowContext();

            RegisterStep(ObtainMainWindow, ThreadType.Ui);
            RegisterStep(PromptForTargetAssemblyPath);
            RegisterStep(ObtainTargetAssembly, ThreadType.Task);
            RegisterStep(InterrogateTargetAssemblyForWorkflows);
            RegisterStep(PromptForWorkflowToGraph, ThreadType.Ui);
            RegisterStep(CreateWorkflowInstance, ThreadType.Task);
            RegisterStep(BuildGraphObjectsForSelectedWorkflow);
            RegisterStep(CreateGraphViewModel, ThreadType.Ui);
        }

        private void ObtainMainWindow()
        {
            Context.MainWindow = (MainWindow)Application.Current.MainWindow;
        }

        private void PromptForTargetAssemblyPath()
        {
            var dialog = new VistaOpenFileDialog
                             {
                                 Multiselect = false
                             };
            dialog.ShowDialog(Context.MainWindow);

            Context.TargetAssemblyPath = dialog.FileName;
        }

        private void ObtainTargetAssembly()
        {
            Context.TargetAssembly = Assembly.LoadFrom(Context.TargetAssemblyPath);
        }

        private void InterrogateTargetAssemblyForWorkflows()
        {
            foreach (var type in Context.TargetAssembly.GetTypes())
            {
                if (type.BaseType != null && type.BaseType.Name.StartsWith("SequentialWorkflow"))
                    Context.WorkflowModels.Add(new Models.Workflow {WorkflowType = type, WorkflowName = type.Name});
            }
        }

        private void PromptForWorkflowToGraph()
        {
            var vm = new WorkflowListViewModel(Context.WorkflowModels);
            var listView = new WorkflowListView {DataContext = vm};
            var window = new Window
                {
                    Title = string.Format("Workflows in {0} (v{1})", Context.TargetAssembly.GetName().Name, Context.TargetAssembly.GetName().Version),
                    Owner = Application.Current.MainWindow,
                    Content = listView,
                    Width = 500,
                    Height = 400,
                    ResizeMode = ResizeMode.NoResize
                };

            var result = window.ShowDialog();
            if(result.HasValue && result.Value)
            {
                Context.SelectedWorkflow = vm.SelectedWorkflow;
            }
            else
            {
                throw new NotSupportedException("Cancellation is not yet supported");
            }
        }

        private void CreateWorkflowInstance()
        {
            var wf = Activator.CreateInstance(Context.SelectedWorkflow.WorkflowType);

            var stepsProperty = Context.SelectedWorkflow.WorkflowType.GetProperty("WorkflowSteps");
            var stepsPropertyValue = stepsProperty.GetValue(wf, null);
            Context.RawSteps = (object[])stepsPropertyValue;
        }

        private void BuildGraphObjectsForSelectedWorkflow()
        {
            var nodes = new List<WorkflowStepNode>(Context.RawSteps.Length) {new StartWorkflowNode()};
            
            // Build nodes
            foreach (var step in Context.RawSteps)
            {
                var stepType = step.GetType();
                var node = new WorkflowStepNode
                               {
                                   StepName = GetPropertyValue<string>(step, "StepName"),
                                   IsCritical = GetPropertyValue<bool>(step, "IsCritical"),
                                   Thread = GetPropertyValue<ThreadType>(step, "Thread"),
                                   IsDelegatedStep = stepType.FullName.Contains("DelegatedWorkflowStep"),
                                   IsConditional = GetPropertyValue<bool>(step, "IsConditional")
                               };

                nodes.Add(node);
            }

            // Join nodes sequentially
            WorkflowStepNode previousNode = null;
            foreach (var node in nodes)
            {
                if (previousNode != null)
                    previousNode.Children.Add(node);

                previousNode = node;
            }
            Context.RootNode = nodes[0];
        }

        private void CreateGraphViewModel()
        {
            Context.GraphViewModel = new WorkflowGraphViewModel(new[] {Context.RootNode});
        }

        private T GetPropertyValue<T>(object obj, string propertyName)
        {
            var property = obj.GetType().GetProperty(propertyName);
            if(property == null)
                throw new InvalidOperationException(string.Format("No such property found: {0}", propertyName));

            var propertyValue = property.GetValue(obj, null);

            return (T) propertyValue;
        }
    }

    internal class GraphWorkflowContext : IWorkflowContext
    {
        public GraphWorkflowContext()
        {
            WorkflowModels = new List<Models.Workflow>();
        }

        public MainWindow MainWindow { get; set; }
        public string TargetAssemblyPath { get; set; }
        public Assembly TargetAssembly { get; set; }
        public List<Models.Workflow> WorkflowModels { get; set; }
        public Models.Workflow SelectedWorkflow { get; set; }
        public object[] RawSteps { get; set; }
        public WorkflowStepNode RootNode { get; set; }
        public WorkflowGraphViewModel GraphViewModel { get; set; }
    }
}
